//
//  YoutubeVideoFinder.swift
//  DownloadTube
//
//  Created by Carlos on 07/10/14.
//  Copyright (c) 2014 Carlos. All rights reserved.
//

import UIKit

class YoutubeVideoFinder: VideoFinderProtocol {
   
    let youtubeKey:String
    let maxResultsPerPage:Int
    let searchVideosService:String
    let detailVideosService:String
    let streamsVideosService:String
    let cipherStreamsVideosService:String
    let regex = NSRegularExpression(pattern:"PT((\\d+)H)?((\\d+)M)?((\\d+)S)?", options: NSRegularExpressionOptions.CaseInsensitive, error: nil)

    init () {
        
        youtubeKey = Properties.data.configValues?.valueForKey("youtubeKey") as String
        maxResultsPerPage = Properties.data.configValues?.valueForKey("maxResultsPerPage") as Int
        searchVideosService = Properties.data.configValues?.valueForKey("searchVideosService") as String
        detailVideosService = Properties.data.configValues?.valueForKey("detailVideosService") as String
        streamsVideosService = Properties.data.configValues?.valueForKey("streamsVideosService") as String
        cipherStreamsVideosService = Properties.data.configValues?.valueForKey("cipherStreamsVideosService") as String
    }
    
    func search (query: String) -> Result<SearchResult, String> {
        
        return search(query, nextResult: nil)
    }
   
    func search (query: String, nextResult: String?) -> Result<SearchResult, String> {
        
        let escapedQuery = query.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
        let searchResult = SearchResult(query: query)
        let responseJSONRaw = searchVideos(escapedQuery, nextResult: nextResult)
        let responseJSON = JSON(data: responseJSONRaw)
        
        if let videos = responseJSON["items"].arrayValue {
            
            var videoIds:[String] = []
            
            for video in videos {
                
                let id = video["id"]["videoId"].stringValue
                videoIds.append(id!)
            }
            
            let videoIdsString = ",".join(videoIds)
            let jsonDetailsData = getVideoDetails(videoIdsString)
            let jsonDetails = JSON(data: jsonDetailsData)

            searchResult.nextQueryToken = responseJSON["nextPageToken"].stringValue

            if let videos = jsonDetails["items"].arrayValue {
            
                for video in videos {

                    let id = video["id"].stringValue
                    let title = video["snippet"]["title"].stringValue
                    let thumbnail = video["snippet"]["thumbnails"]["default"]["url"].URLValue
                    let length = video["contentDetails"]["duration"].stringValue
                    let views = video["statistics"]["viewCount"].stringValue!.toInt()
                    let author = video["snippet"]["channelTitle"].stringValue
                    let formattedLength = formatVideoLength(length!)
                    
                    searchResult.data.append(Video(id: id!, title: title!, author: author!, length: formattedLength, thumbnail: thumbnail!, views: views!))
                }
            }
        }

        return .Success(searchResult)
    }
    
    func getStreams(videoId: String) -> Result<[VideoStream], String> {
    
        var streamsVideo:[VideoStream] = []
        let data = invokeJSONService(streamsVideosService, parameters: "video_id=\(videoId)&eurl=https://youtube.googleapis.com/v/\(videoId)")
        let results = Utils.parseQueryStr(NSString(data: data, encoding: NSUTF8StringEncoding)!)
        let cipherSignature = results ["use_cipher_signature"]! == "True"
        println(videoId)
        if(!cipherSignature){
        
            let streams = results ["url_encoded_fmt_stream_map"]?.stringByRemovingPercentEncoding?.componentsSeparatedByString(",")
            
            if(results["status"]! == "fail"){
                
                return Result.Failure(results["reason"]!.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)
            }
            
            for stream in streams! {
                
                let temp = Utils.parseQueryStr(stream)
                let streamId = temp["itag"]!
                let quality = Utils.videoQA2.qualities[temp["itag"]!.toInt()!]!
                let u = NSURL(string: temp["url"]!.stringByRemovingPercentEncoding!)!
                let codec:String = temp["type"]!.stringByRemovingPercentEncoding!
                
                streamsVideo.append(VideoStream(videoId: videoId, streamId: streamId, videoURL: u, codec: codec, videoQuality: quality))
            }
        
        } else {
            
            let responseJSONRaw = invokeJSONService(cipherStreamsVideosService, parameters: "id=\(videoId)")
            let responseJSON = JSON(data: responseJSONRaw)
            
            if let streams = responseJSON["link"] .dictionaryValue {
                
                for stream in streams {
                   
                    let streamId = stream.0 as String
                    let quality = Utils.videoQA2.qualities[stream.0.toInt()!]!
                    
                    if let streamInfo = stream.1.arrayValue {
                        
                        let u = NSURL(string: streamInfo[0].stringValue!)!
                        let codec = streamInfo[2].stringValue!
                        
                        streamsVideo.append(VideoStream(videoId: videoId, streamId: streamId, videoURL: u, codec: codec, videoQuality: quality))
                    }
                }
                
            }
            
            streamsVideo.sort {
                
                $1.videoQuality.resolution?.stringByPaddingToLength(countElements($1.videoQuality.resolution!)-1, withString: "", startingAtIndex: 0).toInt() < $0.videoQuality.resolution?.stringByPaddingToLength(countElements($0.videoQuality.resolution!)-1, withString: "", startingAtIndex: 0).toInt()
            }
        }
        
        
        if(streamsVideo.isEmpty){
            
            return Result.Failure(Utils.localizedString("There are not available streams for this video"))
        }
        
        return .Success(streamsVideo)
    }
    
    func searchVideos(query: String, nextResult: String?) -> NSData{
       
        var parametersService = "key=\(youtubeKey)&q=\(query)&maxResults=\(maxResultsPerPage)&part=id&type=video&videoSyndicated=true&videoEmbebbed=true"
        
        if(nextResult != nil){
            
            parametersService = "\(parametersService)&pageToken=\(nextResult!)"
        }
        
        return invokeJSONService(searchVideosService, parameters: parametersService)
    }
    
    private func getVideoDetails(videosId: String) -> NSData{
        
        let parametersService = "key=\(youtubeKey)&id=\(videosId)&maxResults=\(maxResultsPerPage)&part=contentDetails,statistics,snippet"
        
        return invokeJSONService(detailVideosService, parameters: parametersService)
    }
    
    
    private func invokeJSONService(path: String, parameters: String) -> NSData {
        
        var urlPath = "\(path)?\(parameters)"
        var url: NSURL = NSURL(string: urlPath)!
        var request: NSURLRequest = NSURLRequest(URL: url)
        var response: AutoreleasingUnsafeMutablePointer<NSURLResponse?> = nil
        var error: AutoreleasingUnsafeMutablePointer<NSErrorPointer?> = nil
        
        
        var dataVal: NSData? =  NSURLConnection.sendSynchronousRequest(request, returningResponse: response, error:nil)
        
        return dataVal!
    }
    
    private func formatVideoLength(videoLength: NSString) -> String {
        
        let result = regex!.firstMatchInString(videoLength, options: nil, range: NSMakeRange(0, videoLength.length))!
        var hours = extractIntFromString(videoLength, range: result.rangeAtIndex(2))
        var minutes = extractIntFromString(videoLength, range: result.rangeAtIndex(4))
        var seconds = extractIntFromString(videoLength, range: result.rangeAtIndex(6))
        
        return hours > 0 ? NSString(format: "%d:%02d:%02d", hours, minutes, seconds) : NSString(format: "%02d:%02d", minutes, seconds)
    }
    
    private func extractIntFromString(source: NSString, range: NSRange) -> Int {
        
        return range.length > 0 ? source.substringWithRange(range).toInt()! : 0
    }
}
